# -*- coding: utf-8 -*-


# Copyright 2009 Larry Resnik, Lasfraka-Elixir

#   This program is free software: you can redistribute it and/or modify
#   it under the terms of the GNU General Public License as published by
#   the Free Software Foundation, either version 3 of the License, or
#   (at your option) any later version.

#   This program is distributed in the hope that it will be useful,
#   but WITHOUT ANY WARRANTY; without even the implied warranty of
#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#   GNU General Public License for more details.

#   You should have received a copy of the GNU General Public License
#   along with this program.  If not, see <http://www.gnu.org/licenses/>.


try:
    from threading import Thread
except ImportError:
    from dummy_threading import Thread
from time import sleep
import os

class BGMPlayer(Thread):
    """A thread that manages music.

    A single tune may be looped but there is also support to have an intro
    and a looping section.
    It can only begin playing when start() is called.
    It stops working altogether when kill() is called.
    """

    def __init__(self, device=None, file=None, file_loop=None):
        """Starts the thread and optionally begins playing a file.

        :parameters:
        device      The Audiere device that loads sound files. When not set,
                no music is loaded. Default is None.
        file        A filename or file-like object which is music to load.
                It consists of the BGM's intro when file_loop is available.
                When not set, no music is loaded. Default is None.
        file_loop   A filename or file-like object which is the section of
                the music that follows the former file's part. When not set,
                the former file (if set) loops on its own. If file_loop is
                provided but file is not, no music is loaded. Default is None.
        """

        Thread.__init__(self)
        self._music_stream = None
        self._second_stream = None
        # This is whether or not the song has reached the looping portion.
        self.__pending_loop = 0
        if file is not None and device is not None:
            self.set_stream(device, file, file_loop)
        # This is the main loop control. The thread ends when it is False.
        self.__alive = 1

    def run(self):
        """Makes the thread act on its own until kill() is called."""
        while self.__alive:
            sleep(0.001)
            if self.__pending_loop and not self._music_stream.playing:
                self._second_stream.play()
                self.__pending_loop = 0
        self.stop_sound()

    def kill(self):
        """Sets up this thread to end its execution."""
        self.__alive = 0

    def set_stream(self, device, file, file_follow=None, playing=1,
                   streaming=1, repeating=1):
        """Assign a tune to play in this thread.

        The current sound stream is stopped before loading from file.
        If the file can not be loaded, the current stream is not affected.

        :parameters:
        device      The Audiere device that loads sound files.
        file        A filename or file-like object which is music to load.
                It plays the music intro when file_loop is passed.
        file_follow
                    A filename or file-like object which is the music that
                follows after file's sound has completed. When not set, the
                music in file loops on its own if repeating is True.
                Default is None.
        playing     A boolean for whether or not to play the sound file
                immediately or not. Default is True.
        streaming   A boolean stating if the file should be streamed or loaded
                all-at-once. Default is True.
        repeating   A boolean for if the sound playback repeats or not. This
                value is COMPLETELY IGNORED if file_loop is set.
                Default is True.
        """

        self.stop_sound()
        try:
            temp_stream = device.open_file(file, streaming)
        except RuntimeError:
            print('Unable to load the sound file.')
            return
        # Do not assign _music_stream yet. There is time to back out.

        if file_follow:
            try:
                temp_second_stream = device.open_file(file_follow)
            except RuntimeError:
                print('Unable to load the sound file.')
                return
            # Both streams were gotten.
            self._second_stream = temp_second_stream
            self._second_stream.repeating = 1
            self._music_stream = temp_stream
            # Begin polling for when the intro is done so we can begin playing
            # the looping portion of the song.
            self.__pending_loop = 1
        else:
            # Only the first stream mattered.
            self._music_stream = temp_stream
            self._music_stream.repeating = repeating
            # There is no sound waiting to be played after the first.
            self.__pending_loop = 0
            # Tests for whether or not to re-enable looping are based on the
            # existence of the second stream.
            self._second_stream = None

        if playing:
            self._music_stream.play()

    def start_sound(self):
        """Begins playing a sound if there is not one playing already."""
        try:
            if self._second_stream:
                if self._second_stream.playing:
                    return
                self.__pending_loop = 1
            self._music_stream.play()
        except AttributeError:
            pass

    def stop_sound(self):
        """Stops playing sound."""
        try:
            if self._second_stream:
                self._second_stream.stop()
            self.__pending_loop = 0
            self._music_stream.stop()
        except AttributeError:
            pass

    def restart_sound(self):
        """Forcibly restarts the entire loaded song."""
        self.stop_sound()
        try:
            self._music_stream.play()
            if self._second_stream:
                self.__pending_loop = 1
        except AttributeError:
            pass

    def set_volume(self, volume):
        try:
            self._music_stream.volume = float(volume) / 100
            if self._second_stream:
                self._second_stream.volume = float(volume) / 100
        except AttributeError:
            pass


class AudiereHandler(object):
    """This is a handler for all sound effects processed through PyAudiere."""
    def __init__(self, audiere_device, file, volume=100):
        try:
            self.sound = audiere_device.open_file(file, 1)
        except RuntimeError:
            print('%s could not be loaded.' % file)
            self.sound = None
        self.set_volume(volume)

    def force_start_sound(self):
        try:
            if self.sound.playing:
                self.sound.reset()
            else:
                self.sound.play()
        except AttributeError:
            pass

    def set_volume(self, volume):
        try:
            self.sound.volume = float(volume) / 100
        except AttributeError:
            pass

# Note: The importing down here violates PEP 8 but accomplishes its task this
# way. The AudiereHandler must be defined first. If importing audiere fails,
# the already defined AudiereHandler may be quietly replaced by NoSound. Calls
# to NoSound will emulate calls to AudiereHandler, so there should be little
# to no code changes if audiere is not available.
try:
    import audiere
except ImportError:
    print('PyAudiere is unavailable. Will use sound with pyglet.')
    import silent_handler
    AudiereHandler = silent_handler.SilentHandler
